﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL;

namespace DDD {
    /// <summary>
    /// <see cref="ShaderProgram"/>クラスに拡張メソッドを追加するためのクラス
    /// </summary>
    public static class ShaderProgramExtentions {

        /// <summary>
        /// シェーダープログラムのコンパイル(とリンク)を行います
        /// </summary>
        /// <param name="sp"><see cref="ShaderProgram"/>オブジェクト</param>
        public static void Compile (this ShaderProgram sp) {

            var vs = sp.VertexShader;
            if (vs != null) {
                vs.Compile ();
            }

            var fs = sp.FragmentShader;
            if (fs != null) {
                fs.Compile ();
            }

            var cache = Graphics3DExtensions.GetGLCache ();
            var id = cache[sp];
            if (id > 0) {
                return;
            }
            ErrorCode err;

            id = GL.CreateProgram ();
            if (id == 0) {
                throw new InvalidOperationException ("glCreateProgram was failed");
            }

            var vert = cache[sp.VertexShader];
            var frag = cache[sp.FragmentShader];

            GL.AttachShader (id, vert);
            if ((err = GL.GetError ()) > 0) {
                throw new InvalidOperationException ("glAttachShader was failed, err=" + err);
            }
            GL.AttachShader (id, frag);
            if ((err = GL.GetError ()) > 0) {
                throw new InvalidOperationException ("glAttachShader was failed, err=" + err);
            }

            // link前
            var varyings = sp.FeedbackVaryings.ToArray();
            GL.TransformFeedbackVaryings (id, varyings.Length, varyings, TransformFeedbackMode.SeparateAttribs);
            if ((err = GL.GetError ()) > 0) {
                throw new InvalidOperationException ("glTransformFeedbackVaryings was failed, err=" + err);
            }

            // Link
            GL.LinkProgram (id);
            if ((err = GL.GetError ()) > 0) {
                throw new InvalidOperationException ("glLinkProgram was failed, err=" + err);
            }
            var log = GL.GetProgramInfoLog (id);
            sp.SetLog (log);

            int status;
            GL.GetProgram (id, ProgramParameter.LinkStatus, out status);
            if (status == 0) {
                throw new InvalidOperationException ("glLinkProgram was failed, msg=" + sp.LinkLog);
            }

            // 使用されているAttribute変数宣言とUniform変数宣言の列挙
            var attrs = new List<ShaderDeclaration> ();
            var unifs = new List<ShaderDeclaration> ();

            int num;
            GL.GetProgram (id, ProgramParameter.ActiveAttributes, out num);
            for (var i = 0; i < num; i++) {
                ActiveAttribType type;
                int arrayLength;
                var name = GL.GetActiveAttrib (id, i, out arrayLength, out type);
                var location = GL.GetAttribLocation (id, name);
                attrs.Add (new ShaderDeclaration (name, type.ToDDD (), arrayLength, location, i));
            }
            sp.SetAttributes (attrs.ToArray ());

            GL.GetProgram (id, ProgramParameter.ActiveUniforms, out num);
            for (var i = 0; i < num; i++) {
                ActiveUniformType type;
                int arrayLength;
                var name = GL.GetActiveUniform (id, i, out arrayLength, out type);
                var location = GL.GetUniformLocation (id, name);
                unifs.Add (new ShaderDeclaration (name, type.ToDDD (), arrayLength, location, i));
            }
            sp.SetUniforms (unifs.ToArray ());

            cache.Add (sp, id);
        }

        /// <summary>
        /// レンダリング
        /// </summary>
        /// <param name="sp"><see cref="ShaderProgram"/>オブジェクト</param>
        public static void Render (this ShaderProgram sp) {
            var cache = Graphics3DExtensions.GetGLCache ();
            var id = cache[sp];
            if (id < 0) {
                throw new InvalidOperationException ("OpenGL id was not found");
            }
            ErrorCode err;

            GL.UseProgram (id);
            if ((err = GL.GetError ()) > 0) {
                throw new InvalidOperationException ("glUseProgram was failed, err=" + err);
            }
        }
    }




}
